Skip to content

feat(identity): email de «crea tu contraseña» para perfiles de donante sin contraseña#420

Open
nopestack wants to merge 5 commits into
GlobalEmergency:mainfrom
nopestack:feature/204-password-setup-email
Open

feat(identity): email de «crea tu contraseña» para perfiles de donante sin contraseña#420
nopestack wants to merge 5 commits into
GlobalEmergency:mainfrom
nopestack:feature/204-password-setup-email

Conversation

@nopestack

Copy link
Copy Markdown
Contributor

Closes #204

Implementa el envío del correo de set-password que quedó como hueco en #168 (PR #201) y el flujo completo para que un donante que se pre-registra sin cuenta pueda crear su contraseña, acceder y ver sus donaciones.

Qué incluye

  • Token de un solo uso con caducidad. Token opaco de 256 bits (base64url); se persiste solo su hash SHA-256 (tabla password_setup_tokens, migración 0059). Caducidad configurable (PASSWORD_SETUP_TTL_HOURS, por defecto 48h).
  • POST /auth/set-password (token + contraseña): valida el token, fija la contraseña, invalida el token y cualquier hermano pendiente del usuario (sin replay) y devuelve un JWT (auto-login).
  • POST /auth/set-password/request (email): reenvío del enlace; responde 202 siempre y solo envía si el email es un perfil sin contraseña. Ambos endpoints con rate-limit (@Throttle).
  • Email bilingüe ES/EN renderizado a través de un puerto EmailSender.
  • Sustituye NoopSetPasswordInviter por EmailSetPasswordInviter real (emite/persiste el token, arma el enlace desde FRONTEND_URL y envía el correo).

Propiedades de seguridad

  • Tokens no adivinables (32 bytes de randomBytes) y de un solo uso (se marca used_at; validez = used_at IS NULL AND expires_at > now()).
  • Se guarda el hash, no el token → una lectura de la BD no filtra enlaces usables.
  • POST /auth/set-password valida existencia + no caducado + no usado antes de permitir fijar la contraseña, e invalida el token inmediatamente tras el éxito (sin replay).
  • Sin oráculo: token inexistente/caducado/usado devuelven el mismo 400 con el mismo cuerpo.
  • A prueba de enumeración: el reenvío responde 202 con independencia de si el email existe o ya tiene contraseña, y está rate-limitado para evitar el email-bombing (consistente con la higiene de Login en este repo).

Verificación

  • TDD: unitarios de dominio (generador/entidad de token), casos de uso (SetPassword, RequestPasswordSetup) e infraestructura (inviter real, plantilla de email), más e2e de POST /auth/set-password cubriendo: token válido (200 + JWT + login posterior OK), caducado (400), ya usado (400), inexistente (400) y replay del mismo token (400).
  • Gate local en verde: pnpm install --frozen-lockfile, pnpm --filter api build, eslint --max-warnings=0, prettier --check, pnpm --filter api test (1653 tests) y la e2e nueva. pnpm gen:api regenerado (contrato con ambas rutas).

Fuera de alcance (seguimiento)

  • Transporte de email real: el adaptador EmailSender por defecto (LoggingEmailSender) registra el mensaje; conectar un transporte SMTP/SES (necesita credenciales de entrega) es el único cambio pendiente y no toca a ningún llamador.
  • Página web de set-password: el enlace apunta a ${FRONTEND_URL}/crear-contrasena?token=...; la página en apps/web que consume POST /auth/set-password queda como seguimiento (API y contrato listos).

nopestack added 5 commits July 6, 2026 09:54
…nante sin contraseña (GlobalEmergency#204)

Implementa el envío real del correo de set-password que quedó como hueco en GlobalEmergency#168,
más el flujo para establecer la contraseña con un token de un solo uso y caducidad.

- Token opaco de 256 bits (base64url), del que solo se guarda el hash SHA-256
  (tabla `password_setup_tokens`, migración 0059). Un enlace nunca queda legible
  en la base de datos.
- `POST /auth/set-password`: valida el token (existe, no caducado, no usado),
  fija la contraseña, invalida el token y sus hermanos (sin replay) y devuelve un
  JWT (auto-login). Token inválido/caducado/usado devuelven el mismo 400 (sin
  oráculo).
- `POST /auth/set-password/request`: reenvío del enlace; responde 202 siempre y
  solo envía si el email corresponde a un perfil sin contraseña (a prueba de
  enumeración). Ambos endpoints con rate-limit.
- Plantilla de email bilingüe ES/EN a través de un puerto `EmailSender`.
- Sustituye `NoopSetPasswordInviter` por `EmailSetPasswordInviter` real, que
  emite y persiste el token y envía el correo. El transporte por defecto registra
  el mensaje (queda pendiente conectar un transporte SMTP/SES real).

TDD: unitarios de dominio/casos de uso e infraestructura + e2e del endpoint
(token válido, caducado, ya usado, inexistente y replay). `pnpm gen:api`
regenerado.
@nopestack
nopestack requested a review from vgpastor as a code owner July 17, 2026 08:49
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@nopestack is attempting to deploy a commit to the GlobalEmergency Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enviar el email de «crea tu contraseña» a los perfiles de donante creados sin contraseña

1 participant